上一個章節我們在探索 forEach() 原始碼的時候,除了使用到泛型、Lambda、inline 等技巧外,其實還有用到 Kotlin 的 Extension 語法。Extension 是 Kotlin 裡一個很重要的特點,Kotlin 的標準函式庫裡也大量的使用這個技巧,甚至 Collection 裡的許多實作也是用 Extension 做出來的。在這個章節裡,我們就要來討論 Extension 的用法。
Extension 是指在不直接修改 Class 定義的情況下,增加 Class 的功能。當我們無法接觸某個 Class 定義,或者該 Class 沒有使用 open 修飾符導致無法繼承時,就是使用 Extension 的最好時機。比方說,我們希望 String 可以多一個 method,但實際上你太可能去修改 Kotlin 原始碼;若是想要用繼承來擴充,一追原始碼也會發現 String 並沒有用 open 修飾符。這時我們就可以用 Extension 來達成!
定義一個 Extension 很簡單,就像宣告一個函式一樣,只是要在函式名稱前面加上接收者類型(Receiver Type)。在下面這一段範例裡,你可以看到函式名稱 surprise() 前多了 String. 的宣告,意思是這個 Extension 是擴充 String 的 method,而 String 就是這個 Extension 的 Receiver。
// 宣告一個 String 的 Extension
fun String.surprise(amount: Int = 3): String {
return this + "!".repeat(amount)
}
// 任何 String 都可以使用這個 method
println("Wow".surprise()) // Wow!!!
值得一提的是,Extension 也適用於繼承,也就是說,假如今天有某個類型繼承 String(假如可以的話),那繼承的 Class 身上也一樣會有 surprise() method 可以呼叫。
Extension 也可以支援泛型,Collection 原始碼裡的 forEach() 就是一個例子:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
我們可以看到 forEach() 是 Iterable<T> 的 Extension,Iterable 本身支援泛型,forEach() 的 Lambda 參數也支援泛型。像這樣的泛型 Extension 在 Kotlin 標準函式庫內很常見,比方說 let() 函式的原始碼是這樣:
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
因為 let() 是 T 的 Extension,所以能夠支援任何型別。相較於把 let() 做成 Any 的 Extension,做成泛型 Extension 不僅可以支援任何型別的 Receiver,同時還保留了 Receiver 的型別資訊,對編譯器來說更加明確。
看完這個章節後,假如對標準函式庫裡的 Extension 有興趣的話,不妨用 IntelliJ IDEA 搜尋 Strings.kt 這個 String 的原始檔,裡面應該就可以看到很多 String 類別的 Extension 宣告。標準函式庫裡的類別 Extension,通常是以類別名稱加 s 結尾來命名,像是 Sequences.kt、Ranges.kt 或 Maps.kt…等。這些檔案提供的功能,都是在各自的類別上以 Extension 擴充的。
像這樣大量使用 Extension 定義核心 API 功能,可以讓標準函式庫既保持輕量,同時還能提供更多的功能。因為一個 Extension 可以適用於多個 Class,所以也能有效的節省空間。
以上 6 章試著從 Collection 原始碼裡深入探索其核心,希望能讓大家更了解 Collection 的底層奧妙。從下一章開始,我們要來討論幾個活用 Collection 的方法。